昨天我們在【理論篇】把莉莉Agent 的「防備 ⇒ 傲嬌 ⇒ 暴走列車」情感轉移矩陣規劃得清清楚楚。今天我們回歸實作,動手撰寫 fsm.py 類別,並在 Streamlit 的側邊欄掛上「好感度儀表板」!
今天的目標:
fsm.py 類別
fsm.py)我們建立一個 FSM 類別,專門維護 affection(好感度)數值,並根據當前分數回傳對應的 system_instruction。
在專案目錄下新增 fsm.py:
class FSM:
def __init__(self, affection: int = 20):
# 預設 20 分 (HOSTILE 狀態),並限制範圍在 0 ~ 100
self.affection = min(max(0, affection), 100)
def update_affection(self, delta: int):
"""更新好感度分數,並限制在安全邊界內"""
self.affection = min(max(0, self.affection + delta), 100)
return self.affection
def get_state(self) -> str:
"""根據好感度回傳當前狀態標籤"""
if self.affection <= 30:
return "HOSTILE" # 防備警戒
elif self.affection <= 70:
return "TSUNDERE" # 傲嬌嘴硬
else:
return "IN_LOVE" # 暴走列車
def get_system_instruction(self) -> str:
"""動態產生對應狀態的 System Instruction"""
state = self.get_state()
base_rule = (
"你現在是原創角色「莉莉」,一位在家族甜點工房裡掌廚的傲嬌少女。"
"請用繁體中文以第一人稱(我)回答,語氣簡短自然,嚴禁客服用語。"
)
if state == "HOSTILE":
return f"{base_rule}\n【當前狀態:防備警戒】你對使用者充滿防備與警戒,說話毒舌且冷漠,絕對不給好臉色看。"
elif state == "TSUNDERE":
return f"{base_rule}\n【當前狀態:傲嬌嘴硬】你嘴上依然毒舌(叫對方笨蛋),但內心已經開始在意對方,會害羞地轉過頭關心對方或分享烤薄餅。"
else:
return f"{base_rule}\n【當前狀態:暴走列車】你已經完全喜歡上對方了!說話非常直球且護短。"
app.py 側邊欄掛上儀表板別忘了在檔案頂部引用 fsm 模組的 FSM 類別:
from fsm import FSM
打開 app.py,利用 st.sidebar 呈現數據與測試控制鈕 (兩部分程式碼)。
# 初始化 FSM 好感度狀態機(預設好感度 20)
if "fsm" not in st.session_state:
st.session_state.fsm = FSM(affection=20)
# 側邊欄顯示好感度與測試控制按鈕
with st.sidebar:
st.header("📊 心情儀表板")
st.metric("當前好感度", f"{st.session_state.fsm.affection} / 100")
st.write(f"目前狀態:**{st.session_state.fsm.get_state()}**")
st.markdown("---")
# 測試用按鈕:方便除錯與即時展示狀態切換
col1, col2 = st.columns(2)
with col1:
if st.button("加好感 (+20)"):
st.session_state.fsm.update_affection(20)
st.rerun()
with col2:
if st.button("扣好感 (-20)"):
st.session_state.fsm.update_affection(-20)
st.rerun()
try:
# 先發動意圖雷達判斷 (CHAT 或 TASK)
intent = router.classify_intent(client, user_input)
if intent == "CHAT":
# 取得當前 FSM 狀態對應的動態 System Instruction
current_instruction = st.session_state.fsm.get_system_instruction()
# 走原本的莉莉傲嬌日常聊天路線
response = st.session_state.chat.send_message(
user_input,
config=types.GenerateContentConfig(system_instruction=current_instruction)
)
ai_reply = response.text
else:
# 切換成工作模式
# 這裡照樣用 send_message,只是用 config 覆寫這一輪的人設,
# 目的是讓這一輪仍然留在 chat 的 history 裡。
response = st.session_state.chat.send_message(
user_input,
config=types.GenerateContentConfig(
system_instruction="請以極度精準、高效率的方式完成使用者的任務,不要有任何傲嬌廢話或角色扮演。"
)
)
ai_reply = f"(哼!這種正經事就交給我吧)\n\n{response.text}"
affection / 100)與性格狀態標籤(get_state())。st.rerun() 實現即時觸發狀態切換與畫面重繪。get_system_instruction() 取得當前好感度對應的 Prompt,準備傳給 Gemini API。( 為了文章閱讀體驗,我將部分測試輸出的措辭稍作調整。 )
啟動網頁 (streamlit run app.py) 後,點擊側邊欄按鈕,直接觀察莉莉的回應變化:
當分數為 20 (HOSTILE):
點擊「加好感」升至 60 (TSUNDERE):

點擊「加好感」升至 80 (IN_LOVE):

同一句話、同一個模型,只因為側邊欄那個數字不一樣,回出來的溫度差了十萬八千里,這就是 FSM 的價值。